/**
 * Measure.c
 * Description: Measures crossing intervals and keeps interval stats
 * Authors: Hamish Cumming, Brian Thorne 2008
 */

#include "measure.h"
#include <stdio.h>
#include <avr\interrupt.h>
#include "kernel.h"
#include "ui.h"

/*  Type definitions */
typedef enum {BUFFER_NOT_READY = 0, BUFFER_READY, BUFFER_INUSE} buffer_status_t;
typedef enum {MINIMUM,MAXIMUM,MEAN} show_stats_t;

/**
 * A buffer element with level crossing data
 */
typedef struct
{
    time_t time;
    uint8_t value;
    buffer_status_t status;
} buf_el_t;

/* Private Functions */
static time_t calculate_crossing_time(volatile buf_el_t* one, volatile buf_el_t* two, uint8_t slope);
static void add_time_to_stats(time_t* time);

/* Private Variables */
volatile static buf_el_t buffer[BUFFER_SIZE]; //the buffer for ADC to write to
static uint8_t write_index = 0;               //which buffer we are writing to
static uint8_t read_index = 0;                //which buffer we are reading from
static stats_t stats;                         //the stats of the current measuring period
static buf_el_t previous = {{0},0,0};         //the most crossing time element
static int8_t show_stats = MEAN;              //which stats is currently being shown

/**
 * ADC Complete Interrupt
 */
ISR (ADC_vect)
{
    /* If the buffer is in use we just have to chuck it out */
    if (buffer[write_index].status == BUFFER_READY)
    {
        buf_el_t buf;
        buf.value = ADCH;
        buf.time = get_clock_value();
        buf.status = BUFFER_READY;
        buffer[write_index] = buf;
        if(++write_index >= BUFFER_SIZE)
            write_index = 0;
        //tell the kernel we would like to do some measuring
        task_flags[MEASURE_TASK] = ACTIVATED;
    }
}

/**
 * Pin Change Interrupt
 */
ISR (PCINT0_vect)
{
    if (PINE &(1<<4)){//only trigger on a rising edge

        ADCSRA |= (1<<ADSC); //start converison
    }
}

/** Initialise the measure module */
void measure_init(void)
{
    //initialise buffers
    for (int i=0; i<BUFFER_SIZE;i++)
        buffer[i].status = BUFFER_READY;
    //reset the stats
    reset_stats();
    //initialise ADC
    ADMUX = (1<<REFS0) | (1<<REFS1) | (1<<MUX0) | (1<<ADLAR); //using internal 1.1V reference
    ADCSRA = (1<<ADEN) /*| (1<<ADPS2) */| (1<<ADPS1) | (1<<ADPS0) | (1<<ADIE); //using a prescalar of 32
    //mask the pin interrupt register
    PCMSK0 = _BV(PCINT4);
    register_task(measure_task, MEASURE_TASK);
}

/** Disables pin change interrupts */
void disable_measure(void)
{
    EIMSK = 0;
}

/** Enables pin change interrupts */
void enable_measure(void)
{
    EIMSK = _BV(PCIE0);
}

/** The function to run when in measure mode. */
uint8_t measure_task(void)
{
    buffer[read_index].status = BUFFER_INUSE;//let the ADC know we are using this buffer
    if (previous.time.time32 != 0)
    {
        uint8_t crossed = false; //whether or not the previous two points crossed the midway point
        time_t crossing_time; //the time (absolute) that the crossing (if any) occurred
        if (previous.value > CROSSING_LEVEL && buffer[read_index].value < CROSSING_LEVEL)
        {//crossed with negative slope
            crossing_time = calculate_crossing_time(&previous, &buffer[read_index],NEGATIVE_SLOPE);
            crossed = true;
        }
        else if (previous.value < CROSSING_LEVEL && buffer[read_index].value > CROSSING_LEVEL)
        {//crossed with positive slope
            crossing_time = calculate_crossing_time(&previous, &buffer[read_index],POSITIVE_SLOPE);
            crossed = true;
        }
        if (crossed)
        {
            if (stats.num_crossings > 0) //make sure its not the first one
            {
                time_t interval; //calculate the time between crossings
                interval.time32 = crossing_time.time32 - stats.previous.time32;
                add_time_to_stats(&interval); //update the stats
            }
            stats.previous = crossing_time; //update the previous crossing time
            stats.num_crossings++;
        }
    }
    previous = buffer[read_index];
    buffer[read_index].status = BUFFER_READY;//tell the ADC it can now use this buffer
    if (++read_index >= BUFFER_SIZE)//increment read index
        read_index = 0;
    //deactivate measure task until next buffer is available
    return EXIT_SLEEP;
}

/**
 * Calculates the crossing time of two buffer elements
 * NOTE: The two buffers MUST be on different sides of the crossing level
 * Params are declared volatile so we can use the static volatile buffer as a param
 * The buffer is guaranteed not to change while its status is marked IN_USE
 */
static time_t calculate_crossing_time (volatile buf_el_t* one, volatile buf_el_t* two, uint8_t slope)
{
    //because we are using unsigned ints, need a different eqn depending on whether the signal is rising or falling
    time_t crossing_time;
    if (slope == POSITIVE_SLOPE)
        crossing_time.time32 = (two->value - one->value)/(two->time.time32 - one->time.time32)*(CROSSING_LEVEL - one->value) + one->time.time32;
    else //NEGATIVE_SLOPE
        crossing_time.time32 = (one->value - two->value)/(two->time.time32 - one->time.time32)*(one->value - CROSSING_LEVEL) + one->time.time32;
    return crossing_time;
}

/**
 * Adds a crossing interval to the stats
 */
static void add_time_to_stats(time_t* interval)
{
    if (stats.num_crossings < 2)
    {
        stats.minimum.time32 = stats.maximum.time32 = stats.mean.time32 = interval->time32;
        return;
    }
    if (interval->time32 < stats.minimum.time32)
    {
        stats.minimum.time32 = interval->time32;
    }
    if (interval->time32 > stats.maximum.time32)
    {
        stats.maximum.time32 = interval->time32;
    }
    stats.mean.time32 = (stats.mean.time32*stats.num_crossings + interval->time32) / (stats.num_crossings + 1);
}

/** Resets the statistics */
void reset_stats(void)
{
    stats.num_crossings = 0;
    previous.time.time32 = 0;
}

/** Get the stats for the current measuring period */
stats_t get_stats(void)
{
    return stats;
}

/** Toggles which stats are shown */
void scroll_stats(int8_t direction)
{
    show_stats += direction;
    if (show_stats > MEAN) show_stats = MINIMUM;
    if (show_stats < MINIMUM) show_stats = MEAN;
    task_flags[REPLAY_TASK] = ACTIVATED;
}

/** The replay task function. */
uint8_t replay_task(void)
{
    switch (show_stats)
    {
        char s[30];
        case MINIMUM:
            sprintf(s,"MIN %lu",stats.minimum.time32);
            ui_message(s);
            break;
        case MAXIMUM:
            //maximum is nearly always double the mean (except at v low frequencies)
            sprintf(s,"MAX %lu",stats.maximum.time32);
            ui_message(s);
            break;
        case MEAN:
            sprintf(s,"MEAN %lu",stats.mean.time32);
            ui_message(s);
            break;
    }
    return EXIT_SLEEP;
}
